#ifndef SEMANTIC

#include "semantic_utils.h"

#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int scnt = 0, ucnt = 0;
char* symbols[MAX_SYMBOL_COUNT];
char* unqsybs[MAX_SYMBOL_COUNT];
Symbol* symbolTable[MAX_SYMBOL_COUNT];

int filled[MAX_SYMBOL_COUNT];

void serror(int type, int lineno, char *msg, char* sym) {
    if (strlen(sym) != 0) {
        printf("Error type %d at line %d: %s \"%s\"\n", type, lineno, msg, sym);
    } else {
        printf("Error type %d at line %d: %s\n", type, lineno, msg);
    }
}

void prepareForSemanticAnalysis(Node* node) {
    /* insert into symbol table for declarations */
    switch (node->elem) {
        case 6: // OptTag
            insertSymbol(node->ch[0]->strval);
            break;
        case 8: // VarDec
            if (node->ch[0]->elem == 27) { // ID
                insertSymbol(node->ch[0]->strval);
            }
            break;
        case 9: // FunDec
            insertSymbol(node->ch[0]->strval);
            break;
        default:
        break;
    }
    // allocate memory for attribute node
    SNode* snode = malloc(sizeof(SNode)); 
    node->snode = snode;
    snode->node = node;

    int i;
    for (i = 0; i < node->cnt; i++) {
        if (node->ch[i] != NULL) {
            prepareForSemanticAnalysis(node->ch[i]);
        }
    }
}

void insertSymbol(char* name) {
    symbols[scnt] = malloc(sizeof(char) * strlen(name));
    strcpy(symbols[scnt++], name);
}

void createSymbolTable() {
    sortSymbols();
    getUnqSymbol();
}

int cmp(const void *s1, const void *s2) {
    return strcmp(*(char **)s1, *(char **)s2);
}

void sortSymbols() {
    qsort(symbols, scnt, sizeof(char *), cmp);
}

void getUnqSymbol() {
    int i;
    unqsybs[ucnt++] = symbols[0];
    for (i = 1; i < scnt; i++) {
        if (strcmp(symbols[i], symbols[i - 1])) {
            unqsybs[ucnt++] = symbols[i];
        }
    }
}

int getSymbolIndex(char* name) {
    int l = 0, r = ucnt, mid;
    int cmpres;
    while (l < r) {
        mid = (l + r) >> 1;
        // printf("mid %d %s, %s\n", mid, unqsybs[mid], name);
        cmpres = strcmp(name, unqsybs[mid]);
        if (!cmpres) {
            break;
        } else {
            if (cmpres < 0) r = mid;
            else l = mid + 1;
        }
    }
    return l >= r ? -1 : mid;
}

void fillError(int status, SNode* snode) {
    switch (status) {
        case SYMBOL_FILL_DONE : break;
        case SYMBOL_ALREADY_DECLARED : serror(3, snode->node->lineno, "Redefined variable", snode->name); break;
        case FUNCTION_ALREADY_DEFINED : serror(4, snode->node->lineno, "Redefined function", snode->name); break;
        case FIELD_ALREADY_DECLARED : snode->fieldErr = 1; serror(15, snode->node->lineno, "Redefined field", snode->name); break;
        case DUPLICATED_STRUCT_NAME : serror(16, snode->node->lineno, "Duplicated name", snode->name); break;
        case STRUCTTURE_NAME : serror(96, snode->node->lineno, "Name used as structure", snode->name); break;
        default : break;
    }
}

void fillSymbols(SNode* specifier, SNode* extdeclist) {
    int fillres;
    while(extdeclist != NULL) {
        typeAssign(specifier, extdeclist);
        fillError(fillSymbol(extdeclist->name, extdeclist->type, 0), extdeclist);
        extdeclist = extdeclist->nexDec;
    }
}

int fillSymbol(char* name, Type* type, int address) {
    // printf("fill symbol: %s\n", name);
    int index = getSymbolIndex(name);
    if (filled[index]) {
        if (!address) { 
            if (symbolTable[index]->type->kind == STRUCTTURE) return STRUCTTURE_NAME;
            else return SYMBOL_ALREADY_DECLARED;
        }
        else if (address == 1)return FIELD_ALREADY_DECLARED;
        else if (address == 2) return DUPLICATED_STRUCT_NAME;
    }

    symbolTable[index] = malloc(sizeof(Symbol));

    symbolTable[index]->name = name;
    symbolTable[index]->type = type;

    filled[index] = 1;
    return SYMBOL_FILL_DONE;
}

// TODO same with FillSymbols
void fillStructSymbols(SNode* specifier, SNode* extdeclist) {
    // printf("fill struct symbols\n");
    int fillres;
    while(extdeclist != NULL) {
        typeAssign(specifier, extdeclist);
        fillError(fillSymbol(extdeclist->name, extdeclist->type, 1), extdeclist);
        extdeclist = extdeclist->nexDec;
    }
}

void fillStruct(SNode* snode) {
    fillError(fillSymbol(snode->name, snode->type, 2), snode);
}

// TODO function paramlist
void fillFunctions(SNode* specifier, SNode* func) {
    fillError(fillFunction(specifier, func), func);
    // SNode* param = func->nexPar;
}

// TODO move typeAssign into main process; 
//      funciton paramlist
//      return struct ???
int fillFunction(SNode* specifier, SNode* func) {
    typeAssign(specifier, func);
    int index = getSymbolIndex(func->name);
    if (filled[index]) return FUNCTION_ALREADY_DEFINED;

    symbolTable[index] = malloc(sizeof(Symbol));
    symbolTable[index]->name = func->name;
    symbolTable[index]->type = func->type;
    symbolTable[index]->args = func->nexPar;

    if (func->type->kind == BASIC) func->type->kind = FUNC_BASIC;
    else printf("error return type\n");

    filled[index] = 1;
    return SYMBOL_FILL_DONE;
}

void typeAssign(SNode* specifier, SNode* var) {
    if(var->type == NULL) { // common
        var->type = specifier->type;
    } else { // array
        Type* arrayType = var->type;
        while(arrayType->info.array.type != NULL) {
            arrayType = arrayType->info.array.type;
        }
        arrayType->info.array.type = specifier->type;
    }
}

void synAssign(SNode* pa, SNode* ch, int type) {
    switch (type) {
        case NAMEVAL:
            pa->name = ch->name;
            break;
        case DATATYPE :
            pa->type = ch->type;
            break;
        case ARRAYTYPE :
            pa->type->info.array.type = ch->type;
            break;
        case ARGUMTYPE :
            pa->nexPar = ch;
            break;
        default:
            break;
    }
}

void lexAssign(SNode* snode, Node* node, int type) {
    switch (type) {
        case NAMEVAL :
            snode->name = malloc(sizeof(char) * strlen(node->strval));
            strcpy(snode->name, node->strval);
            break;
        case BASICTYPE :
            snode->type = malloc(sizeof(Type));
            snode->type->kind = BASIC;
            if (node->strval[0] == 'i') {
                snode->type->info.basic = BASIC_INT;
            } else {
                snode->type->info.basic = BASIC_FLOAT;
            }
            break;
        case ARRAYTYPE :
            snode->type = malloc(sizeof(Type));
            snode->type->kind = ARRAY;
            snode->type->info.array.size = node->intval;
            break;
        case CONSTANTINT :
            snode->type = malloc(sizeof(Type));
            snode->type->kind = BASIC;
            snode->type->constant.intval = node->intval;
            snode->type->info.basic = BASIC_INT;
            break;
        case CONSTANTFLOAT :
            snode->type = malloc(sizeof(Type));
            snode->type->kind = BASIC;
            snode->type->constant.floatval = node->floatval;
            snode->type->info.basic = BASIC_FLOAT;
            break;
        default :
            break;
    }
}

void udfAssign(SNode* snode) {
    snode->type = malloc(sizeof(Type));
    snode->type->kind = UNDEFINED;
}

void tabAssign(SNode* snode, int index, int type) {
    switch (type) {
        case DATATYPE :
            snode->type = symbolTable[index]->type;
            break;
        default :
            break;
    }
}

// MODIFIED
void stuAssign(SNode* snode, Node* node) {
    snode->type = malloc(sizeof(Type));
    snode->name = node->ch[1]->snode->name;
    // snode->name = snode->ch[1]->name;
    snode->type->kind = STRUCTTURE;
    snode->type->info.struc.field = node->ch[3]->snode;
    // snode->type->info.struc.field = snode->ch[3];
}

int isVarStructure(SNode* snode) {
    return snode->type->kind == STRUCTTURE;
}

// TODO
int isFieldDefined(SNode* snode, SNode *field) {
    int index = getSymbolIndex(snode->name);
    if (index == -1 || !filled[index]) {
        return 0;
    }
    SNode* field1 = symbolTable[index]->type->info.struc.field;
    SNode* field2;
    while (field1 != NULL) {
        field2 = field1->nexDec;
        while (field2 != NULL) {
            // printf("field: %s", field2->name);
            if (!strcmp(field2->name, field->name)) {
                field->type = field2->type;
                return 1;
            }
            field2 = field2->nexDec;
        }
        field1 = field1->nexField;
    }
    return 0;
}

int checkField(SNode* opt1, SNode* opt2) {
    SNode* fieldx1 = opt1->type->info.struc.field;
    SNode* fieldy1 = opt2->type->info.struc.field;

    SNode *fieldx2, *fieldy2;
    while (fieldx1 != NULL && fieldy1 != NULL) {
        fieldx2 = fieldx1->nexDec;
        fieldy2 = fieldy1->nexDec;

        while (fieldx2 != NULL && fieldy2 != NULL) {
            if (fieldx2->type->info.basic != fieldy2->type->info.basic) return 0;
            fieldx2 = fieldx2->nexDec;
            fieldy2 = fieldy2->nexDec;
        }
        
        fieldx1 = fieldx1->nexField;
        fieldy1 = fieldy1->nexField;
    }
    if (fieldx1 != NULL || fieldy1 != NULL) return 0;
    return 1;
}

int checkFuncArgs(int index, SNode* cargs) {
    SNode* args = symbolTable[index]->args;
    if (args == NULL) return 0;
    while (args != NULL && cargs != NULL) {
        if (!isOptrandTypeSame(args, cargs)) return 0;
        args = args->nexPar;
        cargs = cargs->nexPar;
    }
    if (args != NULL || cargs != NULL) return 0;
    return 1;
}

/*******************************************************
 * operand chceker
 * 1. not support function return structure
 * 2. return true(no check) when operand is undefined 
 *******************************************************/
int isOptrandTypeSame(SNode* opt1, SNode* opt2) {
    if (opt1->type->kind == UNDEFINED || opt2->type->kind == UNDEFINED) return 1;
    if (opt1->type->kind != opt2->type->kind && opt2->type->kind != FUNC_BASIC) return 0;
    switch (opt1->type->kind) {
        case BASIC :
            return checkBasic(opt1, opt2); 
            break;
        case STRUCTTURE :
            return checkField(opt1, opt2);
            break; 
    }
    return 0;
}
int checkBasic(SNode* opt1, SNode* opt2) {
    return opt1->type->info.basic == opt2->type->info.basic;
}
int isOperandBasicType(SNode* opt) {
    if (opt->type->kind == UNDEFINED) return 1;
    return opt->type->kind == BASIC;
}
int isOperandTypeInt(SNode* opt) {
    if (opt->type->kind == UNDEFINED) return 1;
    return opt->type->kind == BASIC && opt->type->info.basic == BASIC_INT;
}
int isLeftHandVar(SNode* opt) {
    return opt->type->constant.intval == 0 && opt->type->constant.floatval == 0;
}

/*******************************************************
 * operator checker
 *******************************************************/
int isAlgOperator(Node* node) {
    return (
        node->elem == SybPLUS ||
        node->elem == SybMINUS ||
        node->elem == SybSTAR ||
        node->elem == SybDIV
    );
}
int isLogOperator(Node* node) {
    return (
        node->elem == SybAND ||
        node->elem == SybOR ||
        node->elem == SybNOT ||
        node->elem == SybRELOP
    );
}
int isAsnOperator(Node* node) {
    return (
        node->elem == SybASSIGNOP 
    );
}

/*******************************************************
 * printer
 * @showSymbols     show symbols
 * @showSymblos     show symbol table
 * @showSymbol      show info of a symbol in the table
//  * @println         divider printer
 * @showStructField show fileds of structure
 * @showArrayType   show array info
 * @showArgs        show arguments of function
 *******************************************************/
void showSymbols(char* sybs[], int cnt) {
    println();
    int i;
    for (i = 0; i < cnt; i++) {
        printf("%s\n", sybs[i]);
    }
}
void showSymblos() {
    int i;
    for (i = 0; i < ucnt; i++) {
        if(symbolTable[i] != NULL && filled[i]) {
            showSymbol(i);
        }
    }
}
void showSymbol(int index) {
    Symbol* symbol = symbolTable[index];
    printf("symbol name: %s\nsymbol type: ", symbol->name);

    switch (symbol->type->kind) {
        case BASIC : printf("basic %d\n", symbol->type->info.basic); break;
        // case ARRAY : printf("array "); showArrayType(symbol->type); printf("\n"); break;
        case FUNC_BASIC : printf("function %d ", symbol->type->info.basic); showArgs(symbol->args); printf("\n"); break;
        // case STRUCTTURE : printf("structure "); showStructField(symbol->name); printf("\n"); break;
        default : break;
    }
    
}
void println() {
    printf("----------------------------------\n");
}
void showStructField(char* name) {
    int index = getSymbolIndex(name);
    if (index == -1 || !filled[index]) {
        // printf("undefined\n");
        return ;
    }
    printf("{");
    SNode* field1 = symbolTable[index]->type->info.struc.field;
    SNode* field2;
    while (field1 != NULL) {
        field2 = field1->nexDec;
        while (field2 != NULL) {
            // printf("%x\n", field2);
            if (field2->fieldErr) {
                field2 = field2->nexDec;
                continue; 
            }
            printf("type %d %s", field2->type->info.basic, field2->name);
            field2 = field2->nexDec;
            if (field2 != NULL) printf("  ");
        }
        field1 = field1->nexField;
        if (field1 != NULL) printf(" ");
    }
    printf("}");
}
void showArrayType(Type* type) {
    while(type->kind != BASIC) {
        printf("[%d]", type->info.array.size);
        type = type->info.array.type;
    }
    printf(" %d", type->info.basic);
}
void showArgs(SNode* arg) {
    printf("(");
    while(arg != NULL) {
        switch (arg->type->kind) {
            case BASIC : 
                // printf("basic %d %s ", arg->type->info.basic, arg->name); 
                if (arg->type->info.basic == 0) printf("int");
                else printf("float");
                break;
            // case ARRAY : printf("array "); showArrayType(arg->type); printf(" "); break;
            default : break;
        }
        arg = arg->nexPar;
        if (arg != NULL) printf(", ");
    }
    printf(")");
}


int isSymbolArray(char* name) {
    int index = getSymbolIndex(name);
    return symbolTable[index]->type->kind == ARRAY;
}